# Стиль кода

## Использование интерфейсов

Многие разработчики видят смысл интерфейсов в создании базы для различных реализаций одного класса. 
Согласно этой логике, если множественная реализация одного класса не предусмотрена, интерфейсы не нужны. 
Тем не менее, назначение интерфейсов намного шире их роли в применении наследования и полиморфизма.

Согласно "Чистой архитектуре" Роберта Мартина, зависимости между компонентами приложения должны строиться на базе стабильных, устойчивых абстракций. 
Самой стабильной абстракцией является интерфейс, поскольку он не содержит логику, а только декларации. 
В этом аспекте, интерфейсы используются в качестве "скелета" приложения. Такой подход также согласуется с DIP - Dependency Inversion Principle.

Согласно TDD, который мы пытаемся внедрить как стандарт написания кода, сначала пишется интерфейс в рамках общей архитектуры приложения. 
Далее, пишется тест, вызывающий метод интерфейса. 
Тест красный, потому что для метода нет реализации. 
Далее, разработчик пишет реализацию интерфейса, в котором пишет реализацию тестируемого метода. 
Тест успешно проходит и становится зелёным. 
После этого, разработчик рефакторит код. 
Такой цикл разработки известен как "красный - зелёный - рефакторинг".

Помимо очевидной архитектурной функции, использование интерфейсов позволяет добиться положительных побочных эффектов (множественная реализация, мок клиентов и прочих), а также, соблюдения других принципов SOLID (Open-Closed Principle, Liskov Substitution Principle и вышеупомянутого Dependency Inversion Principle).

## Функции и параметры

Мы не используем вызовы функций в качестве параметров функций. 
Функциональный стиль Kotlin позволяет реализовывать полученный результат в цепочке вызовов. 
Если вложенность слишком большая, лучше выделять данные в отдельные переменные.

```kotlin
processRouteService.calculate(processService.findCurrentNode(task)) //плохо
 
processService.findCurrentNode(task)?.let { currentNode -> processRouteService.calculate(currentNode) } //хорошо
 
val currentNode = processService.findCurrentNode(task) //тоже неплохо
processRouteService.calculate(currentNode)
```

Преимущества такого подхода следующие:

1. Так выполняется код в стеке JVM. Сначала вычисляется результат для функции-параметра, потом выполняется основная функция с этим параметром.
2. Таким образом, не нарушается Закон Деметры - функция не узнаёт о существовании другой функции и о параметрах, в неё передаваемых.
3. Такой код является более читаемым.

## Get и find

Если функция-геттер возвращает non-nullable ответ, она начинается с `get` (взять). 
Если ответ nullable, начинается с `find` (найти).

Пример:

```kotlin
override fun find(id: UUID): CalculationType? = transaction {
    CalculationTypeEntity
        .select { CalculationTypeEntity.id eq id }
        .andWhere { CalculationTypeEntity.active eq ActiveType.ACTIVE }
        .limit(1)
        .firstOrNull()
        ?.toCalculationTypeWithoutSubtypes()
}
 
override fun get(id: UUID): CalculationType = repository.find(id)
    ?: throw NoSuchElementException("Calculation type not found by id: $id")
```

При таком подходе, nullability возвращаемого значения закладывается на лексическом уровне. 
Такой подход, например, используется в `String Data JPA`, где методы, возвращающие `Optional`, начинаются с `find`, а методы, возвращающие простое значение - с `get`.

## Тип возвращаемого значения

Если возвращаемое значение не `Unit`, мы всегда пишем его тип. 
В противном случае, понижается читаемость кода, а в некоторых случаях, определить тип невозможно. 
Конечно, интеллектуальные IDE могут подсказать нам возвращаемый тип после некоторых манипуляций, но IDEA не данность, и код должен быть понятен без помощи сторонних инструментов.

Реальный пример отсутствия возвращаемого типа:

```kotlin
fun deleteGroup(groupId: String) = authorizationAdminService.deleteGroup(GROUP_REALM, groupId)
```

В примере выше при ревью кода, скажем, в битбакете, определить тип возвращаемого значения невозможно, поскольку вызываемый метод находится в используемой библиотеке. 
Мы ревьюим много кода и хотим понимать его.

## Функции и методы

В функциональном программировании функция рассматривается в математическом аспекте -` y = f(x)`, где существует отношение между элементами `x` и `y` и изменение значения `x` влечёт за собой изменение значения `y`. 
В языке Java с возвращаемым значением проще - если метод `void`, Вы не можете вернуть значение. 
Поскольку в языке Kotlin ключевое слово `void` заменено классом `Unit`, стало возможно использовать оператор присвоения для функций, возвращающих `Unit`.

Мы пользуемся следующим правилом: если функция возвращает значение, мы используем оператор присваивания; если функция возвращает `Unit`, мы описываем тело такой функции через фигурные скобки; если функция слишком большая и сложная, чтобы можно было использовать оператор присваивания, мы рефакторим эту функцию, пока она не станет маленькой и понятной.

```kotlin
fun get(key: String): ProcessDefinition = repository.find(key) //функция с возвращаемым значением
 
fun deleteGroup(groupId: String) {
    authorizationAdminService.deleteGroup(GROUP_REALM, groupId) //функция без возвращаемого значения
}
```

## Композиция или наследование?

Композиция и наследование - два концептуально разных подхода к расширению функциональности.

**Наследование** подразумевает расширение функциональности суперкласса, при этом, подкласс является его развитием. 
Любой подкласс является суперклассом + дополнительные свойства и функции.

Нагляднее всего это демонстрируется на следующем примере:

```kotlin
open class Animal
 
class Cat : Animal()
 
fun main() {
     
    val cat: Animal = Cat()
}
```

Как видно из примера, поле `cat` является классом `Animal` в реализации `Cat`.

**Композиция** подразумевает расширение функциональности класса за счёт класса-компонента. 
Класс-потребитель **_получает_** функциональность композированного класса через обращение к нему.

Таким образом, отличие наследования и композиции в части отношений заключается в том, что при наследовании класс-наследник **_является_** классом-родителем, а при композиции класс-потребитель **_получает_** композированный класс в качестве своего компонента.

Мы стараемся избегать ненужного наследования там, где это не является необходимым, отдавая предпочтение композиции.

## Использование аббревиатур

Никто не понимает аббревиатуры за пределами команд. 
Некоторые команды составляют собственные глоссарии, но и это не приближает к пониманию аббревиатур для новых членов команд. 
Аббревиатуры делают код менее понятным, даже если значение аббревиатуры очевидно - любая аббревиатура может быть неверно интерпретирована непосвящённым в её смысл.

Вот типичный пример:

```kotlin
enum class ResourceType {
    EXTERNAL, CS
}
```

Что такое `CS`, абсолютно непонятно. `CaseSensitive`? `CommonString`? `CounterStrike`?

Мы против аббревиатур в любом виде. 
Любую аббревиатуру можно оформить в виде простой и понятной комбинации слов.

## Псевдонимы типов (type aliases)

Мы стараемся не использовать без надобности псевдонимы типов.
Мы используем их только при типизации, когда по структуре не очевидно, что означает поле.

Пример:

```kotlin
val personId: UUID //по названию поля понятно, что это идентификатор персоны

val persons: Map<UUID, Person> //по типу не очевидно, чем является ключ

//делаем ключ очевидным
typealias PersonId = UUID
val persons: Map<PersonId, Person>
```

Бесконтрольное применение псевдонимов типов может очень сильно запутать разработчика.

Пример:

```kotlin
abstract fun get(personId: PersonId): Person
```

В данном случае, по псевдониму непонятно, какого именно он типа.
Ответственному разработчику придётся убедиться, что это `UUID`.
Это тот самый случай, когда излишнее использование псевдонимов может существенно усложнить понимание кода.